package com.lpb.spring.lpbspring.getway;

import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import com.alibaba.fastjson.JSON;
import lombok.*;
import org.springframework.web.util.UriComponentsBuilder;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.stream.Collectors;

@Data
public class GenAppSignatureBean {
    public static final String ALGORITHM_HMACSHA256 = "HmacSHA256";
    public static final String ALGORITHM_HMACSHA1 = "HmacSHA1";
    public static final String X_ALT_TIMESTAMP = "x-alt-timestamp";
    private static final char SIGN_SPLIT = '\n';
    private static final String PARAM_KEY = "key";
    private static final String PARAM_ALGORITHM = "algorithm";
    private static final String PARAM_HEADERS = "headers";
    private static final String PARAM_SIGNATURE = "signature";
    private static final Charset UTF_8 = StandardCharsets.UTF_8;
    private static final String SPACE = " ";
    private static final String AUTH_PARAM_SPLIT = ", ";
    /**
     * 被分配的open-app的key，是您的身份标识
     */

    private final String appKey;
    /**
     * 被分配的open-app的私钥，请您自行保证秘钥的安全
     */

    private final String appSecret;
    /**
     * 使用的签名算法，当前支持HmacSHA256,HmacSHA1
     */

    private final String algorithm;
    /**
     * 参与签名的请求头，其中x-alt-timestamp是必填的, 毫秒，请求时至少携带这个头
     */

    private final Map<String, String> signHeaders;
    /**
     * 请求方法，按规范必须大写
     */

    private final String method;
    /**
     * 消息体类型，建议明确指定
     */

    private final String contentType;
    /**
     * 如果请求的contentType是form类型，该值必须设置为true，其他情况设为false
     */

    private final Boolean formData;
    /**
     * 可接收的响应体类型，需要明确直接指定
     */

    private final String accept;
    /**
     * 请求路径，注意不要使用url-encode编码，是decode-path
     */

    private final String path;
    /**
     * 查询参数，注意不要使用url-encode编码，是decode-name和decode-value
     */

    private final Map<String, String> queryParams;
    /**
     * 当请求体的类型不是form时且有请求体时该字段不能是null，需要字符集编码时，使用uft8编码
     */

    private final byte[] body;
    /**
     * form请求参数，请求的content-type是form时，有该字段，注意不要使用url-encode编码，是decode-name和decode-value
     */

    private final Map<String, String> formParams;

    private String signatureStr;
    private String signature;
    private String contentMd5HeaderValue;
    private String authorizationHeaderValue;

    @Builder
    private GenAppSignatureBean(String appKey, String appSecret, String algorithm, Map<String, String> signHeaders, String method, String contentType, Boolean formData, String accept, byte[] body, String path, Map<String, String> queryParams, Map<String, String> formParams) {
        this.appKey = appKey;
        this.appSecret = appSecret;
        this.algorithm = algorithm;
        this.signHeaders = signHeaders;
        this.method = method;
        this.contentType = contentType;
        this.formData = formData;
        this.accept = accept;
        this.body = body;
        this.path = path;
        this.queryParams = queryParams;
        this.formParams = formParams;
    }


    public AppAuthBean createAppAuth() {
        this.contentMd5HeaderValue = createContentMd5();
        this.signatureStr = generateSignatureStr();
        this.signature = createSign(signatureStr);
        this.authorizationHeaderValue = createAuthorizationHeaderValue(signature);
        return new AppAuthBean(signatureStr, authorizationHeaderValue, contentMd5HeaderValue);
    }

    private void appendPathAndFormParams(StringBuilder builder) {
        TreeMap<String, String> combineParams = new TreeMap<>();
        int paramCount = 0;
        if (queryParams != null) {
            paramCount += queryParams.size();
            combineParams.putAll(queryParams);
        }
        if (formData && formParams != null) {
            paramCount += formParams.size();
            combineParams.putAll(formParams);
        }
        if (paramCount != combineParams.size()) {
            throw new IllegalArgumentException("不允许有重复的参数");
        }
        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromPath(this.path);
        combineParams.forEach(uriBuilder::queryParam);
        String uriStr = uriBuilder.build().toUriString();
        builder.append(uriStr);
    }

    private void appendAccept(StringBuilder builder) {
        builder.append(accept).append(SIGN_SPLIT);
    }

    private void appendHttpMethod(StringBuilder builder) {
        builder.append(method.toUpperCase()).append(SIGN_SPLIT);
    }

    private void appendHeaders(StringBuilder builder) {
        Map<String, String> headerMap = signHeaders.entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey().toLowerCase(), Entry::getValue));
        if (!headerMap.containsKey(X_ALT_TIMESTAMP)) {
            throw new IllegalArgumentException("缺失必须的header：" + X_ALT_TIMESTAMP);
        }
        headerMap.entrySet().stream().sorted(Entry.comparingByKey()).forEach(entry -> builder.append(entry.getKey()).append(": ").append(entry.getValue()).append(SIGN_SPLIT));
    }

    private void appendContentMd5(StringBuilder builder) {
        if (this.contentMd5HeaderValue != null) {
            builder.append(contentMd5HeaderValue);
        }
        builder.append(SIGN_SPLIT);
    }

    private String createContentMd5() {
        if (!formData && body != null && body.length > 0) {
            return Base64.getUrlEncoder().encodeToString(DigestUtil.md5(body));
        }
        return null;
    }

    private void appendContentType(StringBuilder builder) {
        if (contentType != null) {
            builder.append(contentType);
            //System.out.println("build-conT="+contentType);
        }
        builder.append(SIGN_SPLIT);
    }


    private String createAuthorizationHeaderValue(String signature) {
        StringBuilder builder = new StringBuilder();
        appendAuthType(builder, "app");
        appendKey(builder, getAppKey());
        appendAlgorithm(builder, getAlgorithm());
        appendHeaderNames(builder, getSignHeaders());
        appendSignature(builder, signature);
        return builder.toString();
    }

    private void appendSignature(StringBuilder builder, String signature) {
        builder.append(PARAM_SIGNATURE).append("=\"").append(signature).append("\"");
    }

    private void appendHeaderNames(StringBuilder builder, Map<String, String> signHeaders) {
        builder.append(PARAM_HEADERS).append("=\"").append(createHeaderStr(signHeaders)).append("\"").append(AUTH_PARAM_SPLIT);
    }

    private void appendAlgorithm(StringBuilder builder, String algorithm) {
        if (!ALGORITHM_HMACSHA256.equals(algorithm) && !ALGORITHM_HMACSHA1.equals(algorithm)) {
            throw new IllegalArgumentException("不支持的算法：" + algorithm);
        }
        builder.append(PARAM_ALGORITHM).append("=\"").append(algorithm).append("\"").append(AUTH_PARAM_SPLIT);
    }

    private void appendKey(StringBuilder builder, String appKey) {
        builder.append(PARAM_KEY).append("=\"").append(appKey).append("\"").append(AUTH_PARAM_SPLIT);
    }

    private void appendAuthType(StringBuilder builder, String app) {
        builder.append(app).append(SPACE);
    }

    private String createHeaderStr(Map<String, String> signHeaders) {
        return signHeaders.keySet().stream().map(String::toLowerCase).collect(Collectors.joining(SPACE));
    }

    private String generateSignatureStr() {
        StringBuilder builder = new StringBuilder();
        appendHeaders(builder);
        appendHttpMethod(builder);
        appendAccept(builder);
        appendContentType(builder);
        appendContentMd5(builder);
        appendPathAndFormParams(builder);
        return builder.toString();
    }

    private String createSign(String signatureStr) {
        HMac hMac = new HMac(HmacAlgorithm.valueOf(this.algorithm), getAppSecret().getBytes(UTF_8));
        byte[] hmacBytes = hMac.digest(signatureStr.getBytes(UTF_8));
        return Base64.getUrlEncoder().encodeToString(hmacBytes);
    }

    @Getter
    @ToString
    @AllArgsConstructor
    @NoArgsConstructor
    public static class AppAuthBean {
        /**
         * 原始的签名串, 该值可能用于日志打印, 签名串纠错等, 不需要给open网关传输
         */
        private String signatureStr;
        /**
         * 发起open-api调用时,Authorization头的值
         */
        private String authorizationHeaderValue;
        /**
         * 发起open-api调用,当http请求体非空或非null时会计算该值,需要使用Content-MD5请求头传递
         */
        private String contentMd5;
    }
}
